package com.suning.sawp.web.filter;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import redis.clients.jedis.Jedis;

import com.suning.framework.sedis.JedisAction;
import com.suning.framework.sedis.JedisClient;
import com.suning.framework.sedis.impl.JedisClientImpl;
import com.suning.ids.client.config.Configuration;
import com.suning.sawp.constants.ConfigConstants;
import com.suning.sawp.constants.ErrorCodeConstants;
import com.suning.sawp.dto.sys.DeviceValidResult;
import com.suning.sawp.dto.sys.StoreManBindDeviceInfo;
import com.suning.sawp.service.util.AESUtils;
import com.suning.sawp.service.util.ConfigUtil;
import com.suning.sawp.service.util.DateUtils;
import com.suning.sawp.service.util.GsonUtils;
import com.suning.vgs.follow.remote.intf.StoreManRemoteService;
import com.suning.vgs.follow.remote.store.StoreManBaseDto;

public class ValidDeviceFilter implements Filter {

    /**
     * 设备绑定zookeeper url配置节点名
     */
    private static final String DEVICE_VALID_URL_KEY = "/config/sawp/deviceValid";
    
    /**
     * 设备绑定redis缓存key前缀
     */
    private static final String STAFF_DEVICE_INFO = "com.suning.sawp:validDeviceId:staffId:";
    
    /**
     * 设备绑定redis，有效期
     */
    private static final int DEVICE_INFO_EXPIRE_TIME = 60 * 60 * 2;
    
    private static final Logger LOGGER = LoggerFactory.getLogger(UrlTrafficFilter.class);
    
    private JedisClient jedisClient;
    
    private StoreManRemoteService storeManRemoteService;
    
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        ApplicationContext ac = WebApplicationContextUtils.getRequiredWebApplicationContext(
                filterConfig.getServletContext());
        if (null == jedisClient) {
            jedisClient = ac.getBean(JedisClientImpl.class);
        }
        
        if (null == storeManRemoteService) {
            storeManRemoteService = ac.getBean(StoreManRemoteService.class);
        }
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
            ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        // 取客户端传输过来设备ID值
        String djDeviceId = httpRequest.getParameter("djDeviceId");
        String staffId = httpRequest.getRemoteUser();
        boolean flag = true;
        String rejectJson = "";
        // 从zookeeper中取设备校验开关
        // 默认关闭
        String deviceValidFlagStr = ConfigUtil.getConfig("/config/sawp/config", "deviceValidFlag", "false");
        boolean deviceValidFlag = Boolean.valueOf(deviceValidFlagStr);
        if (deviceValidFlag) {
            // 取请求的地址
            String uri = httpRequest.getServletPath();
            // 需要设备校验，判断URL是否在需要校验规则列表中
            List<String> patterns = getUrlPatterns(DEVICE_VALID_URL_KEY);
            if (Configuration.matching(uri, patterns)) {
                Object[] args = {staffId, uri, djDeviceId};
                LOGGER.info("ValidDeviceFilter validDevice staffId:{}, uri: {}, djDeviceId:{}, need deviceId valid", args);
                // 解密客户端传过来的加密设备ID
                String reqDeviceId = AESUtils.decodeAndDecrypt(djDeviceId, ConfigConstants.AES_KEY);
                LOGGER.info("ValidDeviceFilter validDevice staffId:{}, deviceId:{}", staffId, reqDeviceId);
                if (StringUtils.isNotBlank(reqDeviceId)) {
                    StoreManBindDeviceInfo cacheDeviceInfo = getCacheDeviceInfo(staffId);
                    if (null == cacheDeviceInfo) {
                        // 店员原先没有绑定设备ID
                        LOGGER.error("ValidDeviceFilter no bind device. staffId:{}, deviceId:{}", staffId, reqDeviceId);
                        DeviceValidResult validResult = new DeviceValidResult();
                        validResult.setErrorCode(ErrorCodeConstants.ERR_0023);
                        rejectJson = GsonUtils.toJson(validResult, false);
                        flag =  false;
                    } else {
                        // 店员原来有绑定设备ID
                        if (!reqDeviceId.equals(cacheDeviceInfo.getDeviceId())) {
                            // 设备ID不一致
                            LOGGER.error("ValidDeviceFilter not same bind device. staffId:{}, deviceId:{}", staffId, reqDeviceId);
                            DeviceValidResult validResult = new DeviceValidResult();
                            validResult.setErrorCode(ErrorCodeConstants.ERR_0022);
                            validResult.setDeviceId(AESUtils.encrypt(
                                    cacheDeviceInfo.getDeviceId(), ConfigConstants.AES_KEY));
                            validResult.setDeviceName(cacheDeviceInfo.getDeviceName());
                            validResult.setBindTime(cacheDeviceInfo.getBindTime());
                            rejectJson = GsonUtils.toJson(validResult, false);
                            flag = false;
                        }
                    }
                } else {
                    LOGGER.error("ValidDeviceFilter deviceId is empty staffId:{}", staffId, reqDeviceId);
                    flag = false;
                    DeviceValidResult validResult = new DeviceValidResult();
                    validResult.setErrorCode(ErrorCodeConstants.ERR_0024);
                    rejectJson = GsonUtils.toJson(validResult, false);
                }
            }
        }
        
        if (flag) {
            // 设备校验通过
            chain.doFilter(httpRequest, httpResponse);
            return;
        } else {
            // 校验不通过
            httpResponse.setContentType("application/json;charset=utf-8");
            httpResponse.addHeader("Cache-Control", "no-cache");
            httpResponse.getOutputStream().write(rejectJson.getBytes("UTF-8"));
            httpResponse.getOutputStream().flush();
            httpResponse.getOutputStream().close();
        }
    }

    /**
     * 
     * 功能描述: 需要校验设备ID的链接规则列表<br>
     * 〈功能详细描述〉
     *
     * @return
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    private List<String> getUrlPatterns(String key) {
        List<String> patterns = new ArrayList<String>();
        Map<String, String> redirectUrlRuleMap = ConfigUtil.getConfigMap(key, null);
        if (null != redirectUrlRuleMap && !redirectUrlRuleMap.isEmpty()) {
            Set<Entry<String, String>> entries = redirectUrlRuleMap.entrySet();
            for (Entry<String, String> entry : entries) {
                patterns.add(entry.getKey());
            }
        }
        
        return patterns;
    }
    
    /**
     * 
     * 功能描述: 根据工号取绑定的设备信息<br>
     * 〈功能详细描述〉
     * 优先取缓存，没有查询
     * @param staffId
     * @return
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    private StoreManBindDeviceInfo getCacheDeviceInfo(final String staffId) {
        StoreManBindDeviceInfo bindDeviceInfo = null;
        String deviceInfo = jedisClient.execute(new JedisAction<String>() {
            @Override
            public String doAction(Jedis jedis) {
                return jedis.get(STAFF_DEVICE_INFO + staffId);
            }
        });
        
        if (StringUtils.isBlank(deviceInfo)) {
            // 如果缓存中没有查找到绑定设备信息，查询DB获取设备信息
            StoreManBaseDto baseDto = 
                    storeManRemoteService.queryStoreManBaseInfoByStaffId(staffId);
            if (null != baseDto && StringUtils.isNotBlank(baseDto.getDeviceId())) {
                bindDeviceInfo = new StoreManBindDeviceInfo(baseDto.getDeviceId(), 
                        baseDto.getDeviceName(), DateUtils.getDateStrByFormat(
                                baseDto.getDeviceBindTime(), DateUtils.ACCURATE_MINUTES_PATTERN));
                // 将DB中查询得到的绑定设备信息存入缓存，有效期设置两小时
                final String deviceJson = GsonUtils.toJson(bindDeviceInfo, false);
                jedisClient.execute(new JedisAction<String>() {
                    @Override
                    public String doAction(Jedis jedis) {
                        jedis.setex(STAFF_DEVICE_INFO + staffId, DEVICE_INFO_EXPIRE_TIME, deviceJson);
                        return null;
                    }
                });
            }
        } else {
            bindDeviceInfo = GsonUtils.fromJson(deviceInfo, StoreManBindDeviceInfo.class);
        }
        
        return bindDeviceInfo;
    }
    
    @Override
    public void destroy() {
        
    }

}
